package cn.itcast.nems.studentrecord.service.impl.studentrecord;

import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.oldems.dto.oldapi.EmsApiResult;
import cn.itcast.nems.oldems.service.sync.OldEmsSyncService;
import cn.itcast.nems.order.api.OrderApi;
import cn.itcast.nems.order.dao.mapper.OrderRefundMapper;
import cn.itcast.nems.order.dto.PaymentFlowDTO;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import cn.itcast.nems.studentrecord.dao.dataobject.StudentRecordFundFlowInfoDO;
import cn.itcast.nems.studentrecord.dao.dataobject.StudentRecordFundFlowSchoolChannelDO;
import cn.itcast.nems.studentrecord.dao.dataobject.StudentTotalRefundableAmountDO;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecordFundFlow;
import cn.itcast.nems.studentrecord.dao.mapper.StudentRecordFundFlowMapper;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordFundFlowDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordFundFlowInfoDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordPaidMessageDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentTotalRefundableAmountDTO;
import cn.itcast.nems.studentrecord.enumeration.FundFlowPaymentTypeEnum;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordFundFlowService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordMessageService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 学籍资金流转明细;资金流：学籍上汇聚和所有凭单上的正反向的所有资金流水。比如报名订单、转班凭单、退费单等。注：有发生资金变化时纪录该表，但只在实际发生时再纪录！ 服务实现类
 * </p>
 *
 * @author liyong
 * @since 2023-11-14
 */
@Service
@Slf4j
@AllArgsConstructor
public class StudentRecordFundFlowServiceImpl extends ServiceImpl<StudentRecordFundFlowMapper, StudentRecordFundFlow> implements StudentRecordFundFlowService {
    private final StudentRecordMessageService studentRecordMessageService;
    private final StudentRecordService studentRecordService;
    private final OrderRefundMapper orderRefundMapper;
    private final OrderApi orderApi;
    private final OldEmsSyncService oldEmsSyncService;
    private static final BigDecimal DENOMINATOR = new BigDecimal("100");

    @Override
    public StudentRecordFundFlow selectByStudentRecordIdPaymentNo(String studentRecordId, String paymentNo) {
        return getBaseMapper().selectByStudentRecordIdPaymentNo(studentRecordId,paymentNo);
    }

    @Override
    public List<StudentRecordFundFlow> selectByStudentRecordIdAndReferenceIdAndReferenceType(String studentRecordId,
                                                                                             String referenceId,
                                                                                             String referenceType) {
        return getBaseMapper().selectByStudentRecordIdAndReferenceIdAndReferenceType(studentRecordId,referenceId ,referenceType);
    }

    @Override
    @Transactional
    public Boolean create(StudentRecordFundFlowDTO flowDTO) {
        String studentRecordId = flowDTO.getStudentRecordId();
        //        final StudentRecordFundFlow flow = super.getBaseMapper().selectByStudentRecordIdPaymentNo(flowDTO.getStudentRecordId(), flowDTO.getPaymentNo());
        //        if (flow != null) {
        //            log.warn("支付流水已经添加过 {}", flowDTO);
        //            return Boolean.FALSE;
        //        }
        StudentRecordFundFlow flowEntity = new StudentRecordFundFlow();
        BeanUtils.copyProperties(flowDTO, flowEntity);
        if (flowDTO.getPaymentType() != null) {
            flowEntity.setPaymentType(flowDTO.getPaymentType().name());
        }
        if (flowDTO.getReferenceType() != null) {
            flowEntity.setReferenceType(flowDTO.getReferenceType().name());
        }
        log.info("flowDTO:{} flowEntity:{}", flowDTO, flowEntity);
        if (!StringUtils.hasText(flowEntity.getCreatedBy())) {
            flowEntity.setCreatedBy(AccountHolder.getRequiredAccount());
        }
        // 获取渠道类型和原支付渠道
        // multi_payment_account_flag 单渠道｜0、多渠道｜1。注：当“支付类型”为“在线支付”、“离线支付”时，该值为“否”。当“支付类型”为“退费”、“退转”、“费用转移”时，判断该学籍下已有流水的“支付渠道”是否已有多个，如果有多个，则该值为“是”。如果只有单个，则该值为“否”
        this.extracted(studentRecordId, flowEntity);

        super.getBaseMapper().insert(flowEntity);
        boolean isComplete = false;
        if (flowDTO.isUpdateStudentRecord() || flowEntity.getPaymentType().equals(ReferenceTypeEnum.FUND_TRANSFER.name())) {
            FundFlowPaymentTypeEnum paymentType = flowDTO.getPaymentType();
            if (FundFlowPaymentTypeEnum.REFUND_TRANSFER != paymentType && flowDTO.getAmount() > 0) {
                if (flowEntity.getPaymentType().equals(ReferenceTypeEnum.FUND_TRANSFER.name())) {
                    // 费用转移：修改学籍 e2_student_record "转入金额 transfer_from_amount"
                    studentRecordService.updateTransferFromAmount(flowDTO.getStudentRecordId(), flowDTO.getAmount());
                } else {
                    isComplete = this.studentRecordService.paid(flowDTO.getStudentRecordId(), flowDTO.getAmount());
                }
                this.sendMessage(flowEntity, isComplete);
            } else if (flowDTO.getAmount() < 0) {
                if (flowEntity.getPaymentType().equals(ReferenceTypeEnum.FUND_TRANSFER.name())) {
                    // 费用转移：修改学籍 e2_student_record “转出金额 transfer_out_amount”
                    studentRecordService.updateTransferOutAmount(flowDTO.getStudentRecordId(), Math.abs(flowDTO.getAmount()));
                } else {
                    this.studentRecordService.refund(flowDTO.getStudentRecordId(), -flowDTO.getAmount());
                }
            }
        }
        return isComplete;
    }

    private void extracted(String studentRecordId, StudentRecordFundFlow flowEntity) {
        final Boolean multiPaymentAccountFlag = flowEntity.getMultiPaymentAccountFlag();
        if (multiPaymentAccountFlag == null) {
            List<StudentRecordFundFlowSchoolChannelDO> studentRecordFundFlowSchoolChannelDOS = selectByStudentRecordIdClazzId(studentRecordId, null);
            if (studentRecordFundFlowSchoolChannelDOS != null && studentRecordFundFlowSchoolChannelDOS.size() == 1) {
                StudentRecordFundFlowSchoolChannelDO studentRecordFundFlowSchoolChannelDO = studentRecordFundFlowSchoolChannelDOS.get(0);
                flowEntity.setOriginPaymentAccountCode(studentRecordFundFlowSchoolChannelDO.getOriginChannelCode());
                flowEntity.setOriginPaymentAccountName(studentRecordFundFlowSchoolChannelDO.getOriginChannelName());
                flowEntity.setMultiPaymentAccountFlag(Boolean.FALSE);
            } else if (studentRecordFundFlowSchoolChannelDOS != null && studentRecordFundFlowSchoolChannelDOS.size() > 1) {
                flowEntity.setMultiPaymentAccountFlag(Boolean.TRUE);
            } else {
                flowEntity.setOriginPaymentAccountCode("");
                flowEntity.setOriginPaymentAccountName("");
                flowEntity.setMultiPaymentAccountFlag(null);
            }
        }
    }

    @Override
    @Transactional
    public Boolean create(StudentRecordFundFlowInfoDTO studentRecordFundFlowInfoDO) {
        String studentRecordId = studentRecordFundFlowInfoDO.getStudentRecordId();
        String paymentNo = studentRecordFundFlowInfoDO.getPaymentNo();
        BigDecimal amount = new BigDecimal(studentRecordFundFlowInfoDO.getAmount());
        BigDecimal charge = new BigDecimal(studentRecordFundFlowInfoDO.getCharge());
        String paymentAccountCode = orderRefundMapper.paymentchannel(studentRecordFundFlowInfoDO.getSchoolId(), studentRecordFundFlowInfoDO.getPaymentAccountName());
        String paymentAccountName = studentRecordFundFlowInfoDO.getPaymentAccountName();
        LocalDateTime dateTime = LocalDateTime.parse(studentRecordFundFlowInfoDO.getBizDate(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        StudentRecordFundFlowDTO result = new StudentRecordFundFlowDTO();
        result.setAmount(amount.multiply(DENOMINATOR).intValue());
        result.setCharge(charge.multiply(DENOMINATOR).intValue());
        result.setStudentRecordId(studentRecordId);
        result.setClazzId(studentRecordFundFlowInfoDO.getClazzId());
        result.setClazzName(studentRecordFundFlowInfoDO.getClazzName());
        result.setReferenceType(ReferenceTypeEnum.OFFLINE_PAY);
        result.setBizDate(dateTime);
        result.setPaymentType(studentRecordFundFlowInfoDO.getPaymentType());
        result.setPaymentNo(paymentNo);
        result.setPaymentMethodCode("");
        result.setPaymentMethodName("");
        result.setPaymentAccountCode(paymentAccountCode);
        result.setPaymentAccountName(paymentAccountName);
        result.setRemark(studentRecordFundFlowInfoDO.getRemark());
        result.setExtend1(studentRecordFundFlowInfoDO.getPosNo());
        result.setExtend2(studentRecordFundFlowInfoDO.getFinanceCorporationName());
        result.setExtend3(studentRecordFundFlowInfoDO.getPaymentTypeName());
        result.setSchoolId(studentRecordFundFlowInfoDO.getSchoolId());
        result.setReferenceId(studentRecordFundFlowInfoDO.getReferenceId());
        result.setReferenceLineId(studentRecordFundFlowInfoDO.getReferenceLineId());
        String orderId = studentRecordService.findOrderId(studentRecordId);
        if (StringUtils.hasText(orderId)) {
            PaymentFlowDTO flowDTO = new PaymentFlowDTO();
            flowDTO.setOrderId(orderId);
            if (result.getPaymentType() != null) {
                flowDTO.setJournalType(result.getPaymentType().name());
            }
            flowDTO.setJournalNo(result.getPaymentNo());
            flowDTO.setAmount(result.getAmount());
            flowDTO.setCharge(result.getCharge());
            flowDTO.setChargeRate(result.getChargeRate());
            flowDTO.setBizDate(dateTime);
            flowDTO.setPaymentMethodCode("");
            flowDTO.setPaymentMethodName("");
            flowDTO.setPaymentAccountCode(paymentAccountCode);
            flowDTO.setPaymentAccountName(paymentAccountName);
            flowDTO.setRemark(result.getRemark());
            cn.itcast.nems.order.dto.StudentRecordFundFlowDTO studentRecordFundFlowDTO = new cn.itcast.nems.order.dto.StudentRecordFundFlowDTO();
            BeanUtils.copyProperties(result, studentRecordFundFlowDTO);
            studentRecordFundFlowDTO.setCharge(charge.multiply(DENOMINATOR).intValue());
            studentRecordFundFlowDTO.setExtend2(studentRecordFundFlowInfoDO.getFinanceCorporationName());
            if (result.getPaymentType() != null) {
                studentRecordFundFlowDTO.setPaymentType(result.getPaymentType().name());
            }
            if (result.getReferenceType() != null) {
                studentRecordFundFlowDTO.setReferenceType(result.getReferenceType());
            }
            flowDTO.setStudentRecordFundFlowDTO(studentRecordFundFlowDTO);
            orderApi.addPaymentFlow(flowDTO);
            // 同步老EMS
            try {
                EmsApiResult emsApiResult = oldEmsSyncService.fundFlow(studentRecordFundFlowInfoDO, result, amount, studentRecordFundFlowInfoDO.getSchoolId(), paymentAccountCode, dateTime, charge);
                Assert.isTrue(emsApiResult.isSuccess(), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.BAD_REQUEST, "补录流水同步老EMS失败！"));
            } catch (RuntimeException e) {
                Assert.isTrue(false, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.BAD_REQUEST, e.getMessage()));
            }
            return Boolean.TRUE;
        }
        result.setUpdateStudentRecord(true);
        this.create(result);
        // 同步老EMS
        try {
            EmsApiResult emsApiResult = oldEmsSyncService.fundFlow(studentRecordFundFlowInfoDO, result, amount, studentRecordFundFlowInfoDO.getSchoolId(), paymentAccountCode, dateTime, charge);
            Assert.isTrue(emsApiResult.isSuccess(), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.BAD_REQUEST, "补录流水同步老EMS失败！"));
        } catch (RuntimeException e) {
            Assert.isTrue(false, BizExceptionProducer.produce(StudentRecordErrorBodyEnum.BAD_REQUEST, e.getMessage()));
        }
        return Boolean.TRUE;
    }

    @Override
    public boolean exsitReference(String studentRecordId, String referenceId, String referenceLineId) {
        final List<StudentRecordFundFlow> flows = super.getBaseMapper().selectByStudentRecordIdReference(studentRecordId, referenceId, referenceLineId);
        return flows != null && !flows.isEmpty();
    }

    @Override
    public Long exsitPaymentNo(String studentRecordId, String paymentNo) {
        final StudentRecordFundFlow flow = super.getBaseMapper()
                .selectByStudentRecordIdPaymentNo(studentRecordId, paymentNo);
        return flow != null ? flow.getId() : null;
    }

    @Override
    public List<StudentRecordFundFlowInfoDO> listStudentRecordFundFlowInfo(String studentRecordId) {
        return baseMapper.listStudentRecordFundFlowInfo(studentRecordId, null);
    }

    @Override
    public List<StudentRecordFundFlow> listStudentRecordFundFlows(Collection<String> recordIdList) {
        return baseMapper.selectList(new QueryWrapper<StudentRecordFundFlow>().lambda()
                .eq(StudentRecordFundFlow::getDeletedFlag, Boolean.FALSE)
                .in(StudentRecordFundFlow::getStudentRecordId, recordIdList));
    }

    //    @Override
//    public Boolean delete(Long id) {
//        return lambdaUpdate().eq(StudentRecordFundFlow::getId, id)
//                .set(StudentRecordFundFlow::getDeletedBy, AccountHolder.getRequiredAccount())
//                .set(StudentRecordFundFlow::getDeletedDatetime, LocalDateTime.now())
//                .set(StudentRecordFundFlow::getDeletedFlag, Boolean.TRUE).update();
//    }
//
    @Override
    public Boolean update(StudentRecordFundFlowDTO studentRecordFundFlowInfoDO) {
        StudentRecordFundFlow studentRecordFundFlow = new StudentRecordFundFlow();
        studentRecordFundFlow.setId(studentRecordFundFlowInfoDO.getStudentRecordFundFlowId());
        String paymentNo = studentRecordFundFlowInfoDO.getPaymentNo();
        BigDecimal amount = new BigDecimal(studentRecordFundFlowInfoDO.getAmount());
        BigDecimal charge = new BigDecimal(studentRecordFundFlowInfoDO.getCharge());
        String paymentAccountCode = orderRefundMapper.paymentchannel(studentRecordFundFlowInfoDO.getSchoolId(), studentRecordFundFlowInfoDO.getPaymentAccountName());
        String paymentAccountName = studentRecordFundFlowInfoDO.getPaymentAccountName();
        studentRecordFundFlow.setAmount(amount.multiply(DENOMINATOR).intValue());
        studentRecordFundFlow.setCharge(charge.multiply(DENOMINATOR).intValue());
        studentRecordFundFlow.setClazzId(studentRecordFundFlowInfoDO.getClazzId());
        studentRecordFundFlow.setClazzName(studentRecordFundFlowInfoDO.getClazzName());
        studentRecordFundFlow.setReferenceType(FundFlowPaymentTypeEnum.OFFLINE_PAY.name());
        studentRecordFundFlow.setBizDate(studentRecordFundFlowInfoDO.getBizDate());
        studentRecordFundFlow.setPaymentType(studentRecordFundFlowInfoDO.getPaymentType().name());
        studentRecordFundFlow.setPaymentNo(paymentNo);
        studentRecordFundFlow.setPaymentMethodCode("");
        studentRecordFundFlow.setPaymentMethodName("");
        studentRecordFundFlow.setPaymentAccountCode(paymentAccountCode);
        studentRecordFundFlow.setPaymentAccountName(paymentAccountName);
        studentRecordFundFlow.setRemark(studentRecordFundFlowInfoDO.getRemark());
        studentRecordFundFlow.setExtend1(studentRecordFundFlowInfoDO.getExtend1());
        studentRecordFundFlow.setExtend2(studentRecordFundFlowInfoDO.getExtend2());
        studentRecordFundFlow.setExtend3(studentRecordFundFlowInfoDO.getExtend3());
        studentRecordFundFlow.setSchoolId(studentRecordFundFlowInfoDO.getSchoolId());
        studentRecordFundFlow.setReferenceId(studentRecordFundFlowInfoDO.getReferenceId());
        studentRecordFundFlow.setReferenceLineId(studentRecordFundFlowInfoDO.getReferenceLineId());
        baseMapper.updateById(studentRecordFundFlow);
        return Boolean.TRUE;
    }

    @Override
    public List<StudentRecordFundFlowSchoolChannelDO> selectByStudentRecordIdClazzId(String studentRecordId, Integer clazzId) {
        return baseMapper.selectByStudentRecordIdClazzId(studentRecordId, clazzId);
    }

    private void sendMessage(StudentRecordFundFlow flowEntity, boolean isCopmlete) {
        StudentRecordPaidMessageDTO message = new StudentRecordPaidMessageDTO();
        message.setStudentRecordId(flowEntity.getStudentRecordId());
        message.setBizDateTime(flowEntity.getBizDate());
        message.setPaidFlag(isCopmlete);
        message.setTenantId(flowEntity.getTenantId());
        message.setSubtenantId(flowEntity.getSubtenantId());
        studentRecordMessageService.studentRecordPaid(message);
    }

    @Override
    public StudentTotalRefundableAmountDTO getStudentTotalRefundableAmountDTO(final Integer studentId) {
        StudentTotalRefundableAmountDTO totalRefundAmountDTO = null;
        StudentTotalRefundableAmountDO actualPaymentRefundAmountDo = getBaseMapper().getStudentActualPaymentAndActualRefund(studentId);
        if (!Objects.isNull(actualPaymentRefundAmountDo)) {
            totalRefundAmountDTO = new StudentTotalRefundableAmountDTO();
            totalRefundAmountDTO.setStudentId(studentId);
            int actualPaymentAmount = Objects.isNull(actualPaymentRefundAmountDo.getActualPaymentAmount()) ? 0 : actualPaymentRefundAmountDo.getActualPaymentAmount();
            totalRefundAmountDTO.setActualPaymentAmount(actualPaymentAmount);
            int actualRefundAmount = Objects.isNull(actualPaymentRefundAmountDo.getActualRefundAmount()) ? 0 : actualPaymentRefundAmountDo.getActualRefundAmount();
            totalRefundAmountDTO.setActualRefundAmount(actualRefundAmount);
            // 学生可退费金额合计 = 学生实际支付总额 - ABS(学生实际退费总额)
            totalRefundAmountDTO.setTotalRefundableAmount(totalRefundAmountDTO.getActualPaymentAmount() - Math.abs(totalRefundAmountDTO.getActualRefundAmount()));
        }

        return totalRefundAmountDTO;
    }
}
